const WIDTH = window.innerWidth
const HEIGHT = window.innerHeight

// 动画帧工具
const animationFrame = function (w) {
  const requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame
  const cancelAnimationFrame = window.cancelAnimationFrame || window.webkitCancelAnimationFrame

  let timer

  if (typeof requestAnimationFrame === 'function') {
    return {
      start(cb) {
        timer = requestAnimationFrame(cb)
      },
      cancel() {
        cancelAnimationFrame(timer)
      }
    }
  } else {
    return {
      start(cb) {
        timer = setTimeout(cb, Math.floor(1000 / 60));
      },
      cancel() {
        clearTimeout(timer)
      }
    }
  }
}

const random = function () {
  return Math.random() > 0.5
}

// canvas工具
const getCanvas = function () {
  const canvas = document.createElement('canvas')
  canvas.width = WIDTH
  canvas.height = HEIGHT
  ctx = canvas.getContext('2d')
  document.body.appendChild(canvas)
  canvas.style.cssText = `position:absolute;left:0;top:0;`
  return {
    canvas,
    ctx
  }
}

// 颜色生成器
const randomColor = (function () {
  const colors = ['#26ccff', '#a25afd', '#ff5e7e', '#88ff5a', '#fcff42', '#ffa62d', '#ff36ff','#22ff5f']
  const length = colors.length
  const rgb = Array.from({ length: 3 })
  // 缓存颜色值
  const rgbMap = {}

  return function () {
    const index = Math.floor(Math.random() * length)
    const color = colors[index]
    if (!rgbMap[color]) {
      rgb.forEach((v, i) => {
        rgb[i] = parseInt(color.slice(i * 2 + 1, i * 2 + 3), 16)
      })
      rgbMap[color] = `rgba(${rgb.join(',')},1)`
      console.log(rgbMap[color])
    }
    return rgbMap[color]
  }
})()

// 音频工具 
const getAudio = function () {
  const audio = document.createElement('audio');
  audio.preload = 'auto'
  audio.volume = 0.8
  audio.style.visibility = 'hidden'
  audio.src = './asset/烟花爆炸.wav'
  let util = {
    canplay: false,
    el: audio,
    play: () => { audio.play() },
    stop: () => {
      audio.pause()
      audio.currentTime = 0
    }
  }
  audio.addEventListener('canplaythrough', function () {
    util.canplay = true
  }, false)

  return util
}

// 形状绘制工具 
const draw = (function () {
  let textIndex = 0

  const shapeUtil = {
    // 方块
    rect(ctx, { x = 0, y = 0, color = `rgba(0,255,255,1)`, length = 7, angle = 0 } = {}) {
      const Xdis = length * Math.sin(angle)
      const Ydis = length * Math.cos(angle)
      ctx.fillStyle = color
      ctx.beginPath()
      const [origin, ...points] = [
        { x, y },
        { x: x + Xdis, y: y + Ydis },
        { x: x + Xdis + length, y: y + Ydis },
        { x: x + length, y: y }
      ].map(({ x, y }) => ({ x: Math.floor(x), y: Math.floor(y) }))

      ctx.moveTo(origin.x, origin.y)
      points.forEach(({ x, y }) => {
        ctx.lineTo(x, y)
      })
      ctx.closePath()
      ctx.fill()
    },
    // 文字
    font(ctx, { x = 0, y = 0, color = `rgba(0,255,255,1)`, fontText = '新年快乐' } = {}) {
      ctx.beginPath()
      ctx.fillStyle = color
      ctx.font = '12px "微软雅黑"'
      if (textIndex > fontText.length - 1) textIndex = 0
      ctx.fillText(fontText[textIndex++], x, y)
    },
    star(ctx, { R = 6, r = 3, x, y, angle = 0, color = `rgba(0,255,255,1)` } = {}) {// R:大圆半径，r:小圆半径，x: x方向圆心位置 y: y方向圆心位置，angle:旋转角度
      ctx.fillStyle = color
      ctx.beginPath();
      for (var i = 0; i < 5; i++) {
        //因为角度是逆时针计算的，而旋转是顺时针旋转，所以是度数是负值。
        ctx.lineTo(x + Math.cos((18 + 72 * i - angle * 50) / 180 * Math.PI) * R,
          y - Math.sin((18 + 72 * i - angle * 50) / 180 * Math.PI) * R);
        ctx.lineTo(x + Math.cos((54 + 72 * i - angle * 50) / 180 * Math.PI) * r,
          y - Math.sin((54 + 72 * i - angle * 50) / 180 * Math.PI) * r);
      }
      ctx.closePath();
      ctx.fill();
    }
  }

  return {
    ...shapeUtil,
    shapes: Object.keys(shapeUtil)
  }
})()

function Firework({ fontText = '新年快乐', shape = undefined } = {}) {
  this.fontText = fontText
  this.shape = shape
  this.frame = animationFrame()
  const { canvas, ctx } = getCanvas()
  this.canvas = canvas
  this.ctx = ctx
  this.fettis = []
  this.audio = getAudio()
  this.start()
}

Firework.prototype = {
  constructor: Firework,
  // 生成小方块
  generateRect(config) {
    draw[config.shape] && draw[config.shape](this.ctx, config)
  },
  // 初始化
  init() {
    this.canvas.style.opacity = 1
    const x = WIDTH * 0.6 * Math.random()
    const y = HEIGHT * (0.3 + 0.5 * Math.random())
    const shape = this.shape || draw.shapes[Math.floor(draw.shapes.length * Math.random())]
    if (shape === 'font') {
      this.canvas.style.transform = ''
    } else {
      this.canvas.style.transform = `rotateY(${random() ? 0 : -180}deg)`
    }
    this.fettis = Array.from({ length: 200 }).map(v => ({
      x,
      y,
      color: randomColor(),
      angle2D: 3 / 2 * Math.PI + Math.random() * 1 / 4 * Math.PI, // 角度
      velocity: 40 + Math.floor(Math.random() * 20), // 速度
      gravity: 2 + Math.random(),// 恒定重力
      decay: 0.7 + Math.random() * 0.2, // 速度衰减率
      tick: 0,
      shape,
      fontText: this.fontText,
      angle: Math.random() * Math.PI,
      totalTick: 200,
    }))
    this.frame.start(() => { this.update() })
    if (this.audio.canplay) {
      this.audio.play()
    }
  },
  // 刷新画布
  update() {
    const { canvas, ctx, fettis } = this
    canvas.style.opacity -= 1 / 200
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    const remain = fettis.filter(fetti => {
      const process = fetti.tick++ / fetti.totalTick
      if (process > 1) return false
      fetti.x += Math.cos(fetti.angle2D * (Math.random() > 0.5 ? 1 : -1)) * fetti.velocity
      fetti.y += Math.sin(fetti.angle2D) * fetti.velocity + fetti.gravity
      fetti.velocity *= fetti.decay
      fetti.angle += 0.1
      this.generateRect(fetti)
      return true
    })
    if (remain.length) {
      this.frame.start(() => { this.update() })
    } else {
      random() ? this.init() : this.stop()
    }
  },
  start() {
    setTimeout(() => { this.init() }, 0 + Math.random() * 8000);
  },
  stop() {
    this.frame.cancel()
    this.start()
  }
}

window.onload = function () {
  new Firework({ fontText: '一夜暴富', shape: 'font' })
  new Firework({ fontText: '一夜暴富', shape: 'font' })
  Array.from({ length: 5 }).map(v => new Firework())
}
